import 'dart:io';
import 'package:xml/xml.dart';
import '../models/map_models.dart';

class TMXParser {
  static Future<TileMap> parseFromFile(String filePath) async {
    final file = File(filePath);
    final xmlString = await file.readAsString();
    return parseFromString(xmlString);
  }

  static TileMap parseFromString(String xmlString) {
    final document = XmlDocument.parse(xmlString);
    final mapElement = document.rootElement;

    final width = int.parse(mapElement.getAttribute('width') ?? '100');
    final height = int.parse(mapElement.getAttribute('height') ?? '100');
    final tileWidth = int.parse(mapElement.getAttribute('tilewidth') ?? '32');
    final tileHeight = int.parse(mapElement.getAttribute('tileheight') ?? '32');

    final tileSets = <TileSet>[];
    final layers = <MapLayer>[];

    // Parse tileSets
    for (final tileSetElement in mapElement.findElements('tileset')) {
      final tileSet = _parseTileSet(tileSetElement);
      tileSets.add(tileSet);
    }

    // Parse layers
    var layerId = 1;
    for (final layerElement in mapElement.findElements('layer')) {
      final layer = _parseLayer(layerElement, layerId.toString(), width, height);
      layers.add(layer);
      layerId++;
    }

    // Parse object groups
    for (final groupElement in mapElement.findElements('objectgroup')) {
      final layer = _parseObjectGroup(groupElement, layerId.toString(), width, height);
      layers.add(layer);
      layerId++;
    }

    return TileMap(
      width: width,
      height: height,
      tileWidth: tileWidth,
      tileHeight: tileHeight,
      tileSets: tileSets,
      layers: layers,
      nextLayerId: layerId,
    );
  }

  static TileSet _parseTileSet(XmlElement element) {
    final firstGid = int.parse(element.getAttribute('firstgid') ?? '1');
    final name = element.getAttribute('name') ?? '';
    final tileWidth = int.parse(element.getAttribute('tilewidth') ?? '32');
    final tileHeight = int.parse(element.getAttribute('tileheight') ?? '32');
    final tileCount = int.parse(element.getAttribute('tilecount') ?? '0');
    final columns = int.parse(element.getAttribute('columns') ?? '0');

    String imagePath = '';
    final imageElement = element.findElements('image').firstOrNull;
    if (imageElement != null) {
      imagePath = imageElement.getAttribute('source') ?? '';
    }

    return TileSet(
      name: name,
      imagePath: imagePath,
      tileWidth: tileWidth,
      tileHeight: tileHeight,
      tileCount: tileCount,
      columns: columns,
      firstGid: firstGid,
    );
  }

  static MapLayer _parseLayer(XmlElement element, String id, int width, int height) {
    final name = element.getAttribute('name') ?? 'Layer';
    final visible = element.getAttribute('visible') != '0';
    final opacity = double.parse(element.getAttribute('opacity') ?? '1.0');

    final dataElement = element.findElements('data').first;
    final encoding = dataElement.getAttribute('encoding') ?? 'csv';
    
    List<List<int>> data;
    if (encoding == 'csv') {
      data = _parseCSVData(dataElement.innerText, width, height);
    } else {
      // Default empty data
      data = List.generate(height, (y) => List.generate(width, (x) => 0));
    }

    return MapLayer(
      id: id,
      name: name,
      width: width,
      height: height,
      data: data,
      visible: visible,
      opacity: opacity,
    );
  }

  static MapLayer _parseObjectGroup(XmlElement element, String id, int width, int height) {
    final name = element.getAttribute('name') ?? 'ObjectGroup';
    
    // Object groups are represented as empty tile layers for now
    final data = List.generate(height, (y) => List.generate(width, (x) => 0));

    return MapLayer(
      id: id,
      name: name,
      width: width,
      height: height,
      data: data,
      isObjectLayer: true,
    );
  }

  static List<List<int>> _parseCSVData(String csvData, int width, int height) {
    final cleanData = csvData.trim().replaceAll('\n', '').replaceAll(' ', '');
    final values = cleanData.split(',').map((s) => int.tryParse(s) ?? 0).toList();

    final data = <List<int>>[];
    for (int y = 0; y < height; y++) {
      final row = <int>[];
      for (int x = 0; x < width; x++) {
        final index = y * width + x;
        if (index < values.length) {
          row.add(values[index]);
        } else {
          row.add(0);
        }
      }
      data.add(row);
    }

    return data;
  }

  static String generateTMX(TileMap map) {
    final builder = XmlBuilder();
    builder.processing('xml', 'version="1.0" encoding="UTF-8"');
    
    builder.element('map', nest: () {
      builder.attribute('version', map.version);
      builder.attribute('orientation', map.orientation);
      builder.attribute('renderorder', map.renderOrder);
      builder.attribute('width', map.width.toString());
      builder.attribute('height', map.height.toString());
      builder.attribute('tilewidth', map.tileWidth.toString());
      builder.attribute('tileheight', map.tileHeight.toString());
      builder.attribute('nextlayerid', map.nextLayerId.toString());
      builder.attribute('nextobjectid', map.nextObjectId.toString());

      // Add tileSets
      for (final tileSet in map.tileSets) {
        builder.element('tileset', nest: () {
          builder.attribute('firstgid', tileSet.firstGid.toString());
          builder.attribute('name', tileSet.name);
          builder.attribute('tilewidth', tileSet.tileWidth.toString());
          builder.attribute('tileheight', tileSet.tileHeight.toString());
          builder.attribute('tilecount', tileSet.tileCount.toString());
          builder.attribute('columns', tileSet.columns.toString());
          
          builder.element('image', nest: () {
            builder.attribute('source', tileSet.imagePath);
            builder.attribute('width', (tileSet.columns * tileSet.tileWidth).toString());
            builder.attribute('height', ((tileSet.tileCount / tileSet.columns).ceil() * tileSet.tileHeight).toString());
          });
        });
      }

      // Add layers
      for (final layer in map.layers) {
        if (layer.isObjectLayer) {
          builder.element('objectgroup', nest: () {
            builder.attribute('id', layer.id);
            builder.attribute('name', layer.name);
          });
        } else {
          builder.element('layer', nest: () {
            builder.attribute('id', layer.id);
            builder.attribute('name', layer.name);
            builder.attribute('width', layer.width.toString());
            builder.attribute('height', layer.height.toString());
            if (!layer.visible) {
              builder.attribute('visible', '0');
            }
            if (layer.opacity != 1.0) {
              builder.attribute('opacity', layer.opacity.toString());
            }

            builder.element('data', nest: () {
              builder.attribute('encoding', 'csv');
              final csvData = layer.data
                  .expand((row) => row)
                  .join(',');
              builder.text(csvData);
            });
          });
        }
      }
    });

    return builder.buildDocument().toXmlString(pretty: true);
  }

  static Future<void> saveToFile(TileMap map, String filePath) async {
    final tmxContent = generateTMX(map);
    final file = File(filePath);
    await file.writeAsString(tmxContent);
  }
}